package com.cn.qgx428.lazydiary.Utils

/**
 * Created by qinguanxiong on 2017/12/28.
 */

import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.ImageFormat
import android.graphics.Matrix
import android.graphics.Paint
import android.graphics.PorterDuff
import android.graphics.PorterDuffXfermode
import android.graphics.Rect
import android.graphics.RectF
import android.graphics.YuvImage
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.media.ExifInterface
import android.os.Build
import android.util.Log

import java.io.ByteArrayOutputStream
import java.io.File
import java.io.FileInputStream
import java.io.FileNotFoundException
import java.io.IOException

/**
 * 处理bitmap压缩
 */
class BitmapUtils {

    // 每个bitmap占用运行时内存不超过500k
    private val MAX_SIZE = 500 * 1024
    // 图片分界线
    private val FILE_SIZE_BOUNDARY = 500 * 1024

    // bitmap最大宽高
    private val MAX_WIDTH = 1200
    private val MAX_HEIGHT = 1600

    private val currentSize = 0
    private var currentBitmap: Bitmap? = null

    private val size: Int
        get() = if (Build.VERSION.SDK_INT > 19)
            currentBitmap!!.allocationByteCount
        else
            currentBitmap!!.byteCount

    /**
     * 解决bitmap占用的运行时内存
     * @param path
     * @return
     */
    fun compressForCamera(path: String): Bitmap? {

        var file: File? = File(path)
        val underLine: Boolean
        try {
            val inf = FileInputStream(file!!)
            underLine = inf.available() > FILE_SIZE_BOUNDARY
            inf.close()
            file = null
        } catch (e: FileNotFoundException) {
            e.printStackTrace()
        } catch (e: IOException) {
            e.printStackTrace()
        }

        //直接通过采样比暴力压缩图片，得到合适的尺寸再将bitmap载入内存
        val options = BitmapFactory.Options()
        options.inJustDecodeBounds = true
        BitmapFactory.decodeFile(path, options)
        options.inSampleSize = calculateSamplesSize(options)
        options.inJustDecodeBounds = false
        // 用RGB_565读取图片, 这样真的好吗？
        // 存在一个问题，如果图片本身足够小，没有必要用这种方式解码bitmap
        // 但是先加载，再计算，不合适再加载，真的好吗？
        options.inPreferredConfig = Bitmap.Config.RGB_565
        currentBitmap = BitmapFactory.decodeFile(path, options)

        // 尺寸还是超过缩放压缩
        if (size > MAX_SIZE) {
            compressByMatrix(0.5f)
        }

        return currentBitmap
    }

    fun calculateSamplesSize(options: BitmapFactory.Options): Int {
        var height = options.outHeight
        var width = options.outWidth
        var inSample = 1
        Log.d(TAG, "calculateSamplesSize: $height $width")
        if (height > width) {
            while (height > MAX_HEIGHT) {
                inSample *= 2
                height /= 2
            }

        } else {
            while (width > MAX_WIDTH) {
                inSample *= 2
                width /= 2
            }

        }
        Log.d(TAG, "calculateSamplesSize: $inSample")
        return inSample
    }

    /**
     * 缩放法进行压缩
     * MAX_WIDTH 和 MAX_HEIGHT 预留出了足够的空间
     * 让进行缩放的bitmap不至于太小
     * @return
     */
    private fun compressByMatrix(scale: Float) {
        val matrix = Matrix()
        matrix.setScale(scale, scale)
        currentBitmap = Bitmap.createBitmap(currentBitmap!!, 0, 0,
                currentBitmap!!.width, currentBitmap!!.height, matrix, true)
        Log.d(TAG, "compressByMatrix: " + currentBitmap!!.byteCount)

    }

    companion object {


        /**
         * 分界线
         */

        private val TAG = "BitmapUtils "
        private val Log_TAG = "BitmapUtils "
        private val MAX_PHOTO_WIDTH = 1024
        private val MAX_PHOTO_HEIGHT = 1024

        fun makeBitmap(jpegData: ByteArray, maxNumOfPixels: Int): Bitmap? {
            try {
                val options = BitmapFactory.Options()
                options.inJustDecodeBounds = true
                BitmapFactory.decodeByteArray(jpegData, 0, jpegData.size,
                        options)
                if (options.mCancel || options.outWidth == -1
                        || options.outHeight == -1) {
                    return null
                }
                options.inSampleSize = computeSampleSize(
                        options, -1, maxNumOfPixels)
                options.inJustDecodeBounds = false

                options.inDither = false
                options.inPreferredConfig = Bitmap.Config.ARGB_8888
                return BitmapFactory.decodeByteArray(jpegData, 0, jpegData.size,
                        options)
            } catch (ex: OutOfMemoryError) {
                return null
            }

        }

        //	/**
        //	 * Decode image from a image file.
        //	 *
        //	 * @param fileName 	The path of the image file.
        //	 * @param width 	The width which the image file should fit in.
        //	 * @param height  	The height which the image file fit to.
        //	 * @return The instance of the image file.
        //	 *
        //	 */
        fun decodeBitmapFromFile(fileName: String, minWidth: Int, minHeight: Int): Bitmap {
            val opts = BitmapFactory.Options()
            opts.inJustDecodeBounds = true
            BitmapFactory.decodeFile(fileName, opts)
            val x = opts.outWidth.toFloat() / minWidth
            val y = opts.outHeight.toFloat() / minHeight
            if (x <= 1 || y <= 1)
                opts.inSampleSize = 1
            else
                opts.inSampleSize = if (x > y) y.toInt() else x.toInt()
            opts.inJustDecodeBounds = false
            return BitmapFactory.decodeFile(fileName, opts)
        }

        // Rotates the bitmap by the specified degree.
        // If a new bitmap is created, the original bitmap is recycled.
        fun bitmapRotate(b: Bitmap?, degrees: Int): Bitmap? {
            var b = b
            if (degrees != 0 && b != null) {
                val m = Matrix()
                m.setRotate(degrees.toFloat(),
                        b.width.toFloat() / 2, b.height.toFloat() / 2)
                try {
                    val b2 = Bitmap.createBitmap(
                            b, 0, 0, b.width, b.height, m, true)
                    if (b != b2) {
                        b.recycle()
                        b = b2
                    }
                } catch (ex: OutOfMemoryError) {
                    // We have no memory to rotate. Return the original bitmap.
                }

            }
            return b
        }

        fun bitmapFlip(b: Bitmap?, bFlipX: Boolean, bFilpY: Boolean): Bitmap? {
            var b = b
            if (b != null) {
                val m = Matrix()
                m.preScale((if (bFlipX) -1 else 1).toFloat(), (if (bFilpY) -1 else 1).toFloat())
                try {
                    val b2 = Bitmap.createBitmap(
                            b, 0, 0, b.width, b.height, m, true)
                    if (b != b2) {
                        b.recycle()
                        b = b2
                    }
                } catch (ex: OutOfMemoryError) {
                    // We have no memory to rotate. Return the original bitmap.
                }

            }
            return b
        }

        fun bitmapCrop(bmpSrc: Bitmap?, rect: Rect?): Bitmap? {
            if (bmpSrc == null || rect == null)
                return null

            val bm = Bitmap.createBitmap(rect.right - rect.left, rect.bottom - rect.top, bmpSrc.config)
            val canvas = Canvas(bm)
            val dst = Rect(0, 0, rect.right - rect.left, rect.bottom - rect.top)
            canvas.drawBitmap(bmpSrc, rect, dst, null)
            return bm
        }

        fun bitmapMerge(context: Context, dst: Bitmap, src: Bitmap,
                        iconRect: Rect) {
            val icon = BitmapDrawable(context.resources, src)
            bitmapMerge(dst, icon, iconRect)
        }

        fun bitmapMerge(context: Context, dst: Bitmap, iconId: Int,
                        iconRect: Rect) {
            val icon = context.resources.getDrawable(iconId)
            bitmapMerge(dst, icon, iconRect)
        }

        fun bitmapMerge(dst: Bitmap, icon: Drawable, iconRect: Rect) {
            val c = Canvas(dst)
            icon.bounds = iconRect
            icon.draw(c)
        }

        fun toRoundCorner(bitmap: Bitmap, cornerRadius: Int): Bitmap {
            val output = Bitmap.createBitmap(bitmap.width, bitmap.height, Bitmap.Config.ARGB_8888)
            val canvas = Canvas(output)

            val color = Color.WHITE
            val paint = Paint()
            val rect = Rect(0, 0, bitmap.width, bitmap.height)
            val rectF = RectF(rect)
            val roundPx = cornerRadius.toFloat()

            paint.isAntiAlias = true
            canvas.drawARGB(0, 0, 0, 0)
            paint.color = color
            canvas.drawRoundRect(rectF, roundPx, roundPx, paint)

            paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN)
            canvas.drawBitmap(bitmap, rect, rect, paint)

            return output
        }

        fun computeSampleSize(options: BitmapFactory.Options,
                              minSideLength: Int, maxNumOfPixels: Int): Int {
            val initialSize = computeInitialSampleSize(options, minSideLength,
                    maxNumOfPixels)

            var roundedSize: Int
            if (initialSize <= 8) {
                roundedSize = 1
                while (roundedSize < initialSize) {
                    roundedSize = roundedSize shl 1
                }
            } else {
                roundedSize = (initialSize + 7) / 8 * 8
            }

            return roundedSize
        }

        private fun computeInitialSampleSize(options: BitmapFactory.Options,
                                             minSideLength: Int, maxNumOfPixels: Int): Int {
            val w = options.outWidth.toFloat()
            val h = options.outHeight.toFloat()

            val lowerBound = if (maxNumOfPixels < 0)
                1
            else
                Math.ceil(Math.sqrt((w * h / maxNumOfPixels).toDouble())).toInt()
            val upperBound = if (minSideLength < 0)
                128
            else
                Math.min(Math.floor((w / minSideLength).toDouble()),
                        Math.floor((h / minSideLength).toDouble())).toInt()

            if (upperBound < lowerBound) {
                // return the larger one when there is no overlapping zone.
                return lowerBound
            }

            return if (maxNumOfPixels < 0 && minSideLength < 0) {
                1
            } else if (minSideLength < 0) {
                lowerBound
            } else {
                upperBound
            }
        }

        @Synchronized
        fun rawByteArray2RGBABitmap(data: ByteArray, width: Int, height: Int): Bitmap? {
            var bmp: Bitmap? = null
            try {
                val image = YuvImage(data, ImageFormat.NV21, width, height, null)
                if (image != null) {
                    val stream = ByteArrayOutputStream()
                    image.compressToJpeg(Rect(0, 0, width, height), 100, stream)
                    bmp = makeBitmap(stream.toByteArray(), 50 * 1024)
                    stream.close()
                }
            } catch (e: Exception) {
                e.printStackTrace()
            }

            return bmp
        }

        @Synchronized
        fun rawByteArray2RGBABitmap2(data: ByteArray, width: Int, height: Int): Bitmap {
            val frameSize = width * height
            val rgba = IntArray(frameSize)
            for (i in 0 until height) {
                for (j in 0 until width) {
                    var y = 0xff and data[i * width + j].toInt()
                    val u = 0xff and data[frameSize + (i shr 1) * width + (j and 1.inv()) + 0].toInt()
                    val v = 0xff and data[frameSize + (i shr 1) * width + (j and 1.inv()) + 1].toInt()
                    y = if (y < 16) 16 else y
                    var r = Math.round(1.164f * (y - 16) + 1.596f * (v - 128))
                    var g = Math.round(1.164f * (y - 16) - 0.813f * (v - 128) - 0.391f * (u - 128))
                    var b = Math.round(1.164f * (y - 16) + 2.018f * (u - 128))
                    r = if (r < 0) 0 else if (r > 255) 255 else r
                    g = if (g < 0) 0 else if (g > 255) 255 else g
                    b = if (b < 0) 0 else if (b > 255) 255 else b
                    rgba[i * width + j] = -0x1000000 + (b shl 16) + (g shl 8) + r
                }
            }
            val bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
            bmp.setPixels(rgba, 0, width, 0, 0, width, height)
            return bmp
        }



        private fun decode(path: String, width: Int, height: Int): Bitmap? {
            val options = BitmapFactory.Options()
            options.inJustDecodeBounds = true
            BitmapFactory.decodeFile(path, options)
            if (options.outHeight <= 0 || options.outWidth <= 0)
                return null

            val max = Math.max(width, height)

            val sWidth = options.outWidth / max
            val sHeight = options.outHeight / max

            var s = if (sWidth >= sHeight) sWidth else sHeight
            if (s < 1)
                s = 1

            options.inSampleSize = s
            options.inJustDecodeBounds = false
            options.inDither = false
            options.inPreferredConfig = Bitmap.Config.ARGB_8888

            try {
                return BitmapFactory.decodeFile(path, options)
            } catch (e: Exception) {
                e.printStackTrace()
            } catch (e: Error) {
                e.printStackTrace()
            }

            return null
        }

        private fun handleOrientation(bitmap: Bitmap, path: String): Bitmap? {
            var exif: ExifInterface? = null
            try {
                exif = ExifInterface(path)
            } catch (e: IOException) {
                e.printStackTrace()
            }

            var degrees = 0
            if (exif != null) {
                val orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED)
                if (orientation == ExifInterface.ORIENTATION_ROTATE_180) {
                    degrees = 180
                } else if (orientation == ExifInterface.ORIENTATION_ROTATE_90) {
                    degrees = 90
                } else if (orientation == ExifInterface.ORIENTATION_ROTATE_270) {
                    degrees = 270
                }
            }

            return if (degrees == 0) bitmap else BitmapUtils.rotate(bitmap, degrees)

        }

        fun rotate(b: Bitmap?, degrees: Int): Bitmap? {
            var b = b
            if (degrees != 0 && b != null) {
                val m = Matrix()
                m.setRotate(degrees.toFloat(), b.width.toFloat() / 2, b.height.toFloat() / 2)
                try {
                    val b2 = Bitmap.createBitmap(b, 0, 0, b.width, b.height, m, true)
                    if (b != b2) {
                        b.recycle()
                        b = b2
                    }
                } catch (er: Error) {
                    er.printStackTrace()
                }

            }
            return b
        }



        fun bmpToByteArray(bmp: Bitmap, needRecycle: Boolean): ByteArray {
            val output = ByteArrayOutputStream()
            bmp.compress(Bitmap.CompressFormat.PNG, 100, output)
            if (needRecycle) {
                bmp.recycle()
            }

            val result = output.toByteArray()
            try {
                output.close()
            } catch (e: Exception) {
                e.printStackTrace()
            }

            return result
        }
    }
}
